﻿#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;

//5.单继承和多继承关系的虚函数表
//5.1 单继承中的虚函数表
//class Base {
//public:
//	virtual void func1() { cout << "Base::func1" << endl; }
//	virtual void func2() { cout << "Base::func2" << endl; }
//private:
//	int a;
//};
//
//class Derive :public Base {
//public:
//	virtual void func1() { cout << "Derive::func1" << endl; }
//	virtual void func3() { cout << "Derive::func3" << endl; }
//	virtual void func4() { cout << "Derive::func4" << endl; }
//	void func5() { cout << "Derive::func5" << endl; }
//
//private:
//	int b;
//};
//
//class X :public Derive {
//public:
//	virtual void func3() { cout << "X::func3" << endl; }
//};

//虚函数的地址一定会被放进虚函数表吗
//int main() {
//	Base b;
//	Derive d;
//	//通过监视窗口中发现d对象中看不见func3和func4
//	//父类并不总是Base，X的父类是Derive
//	//Derive并不总是子类，它还是X的父类
//
//	X x;//x中也看不到func3
//	//验证func3是否可以多态调用
//	Derive* p = &d;
//	p->func3();
//	p = &x;
//	p->func3();//经验证，可以实现多态调用
//	//说明vs2019只展示被实际重写或与基类相关的虚函数。
//	return 0;
//}

//打印虚表中函数地址
//typedef void(*VFPTR) ();//重命名该类型的函数指针void(*)() 为 VFPTR
//void PrintVFT(VFPTR a[]) { //a是一个数组，每个元素是函数指针
//	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
//	cout << "虚表地址>" << a << endl;
//	for (int i = 0; a[i] != nullptr; ++i) {
//		//a[i]是虚函数地址，光有地址还不够，需要用地址去调用虚函数才能确定
//		printf("第%d个虚函数地址：0x%p -> ", i + 1, a[i]);//a[i]是虚函数表中第i个槽位（slot）存储的函数地址
//		VFPTR f = a[i];//从虚函数表（vtable）中获取第i个虚函数的地址，并将其赋值给函数指针f。
//		f();//这里通过函数指针f直接调用该虚函数。
//		//由于f指向的是虚函数表中的具体实现，所以实际调用的是对象（Base或Derive）对应的虚函数。
//	}
//	cout << endl;
//}
//int main() {
//	Base b;
//	Derive d;
//	X x;
//	// 思路：取出b、d对象的头4bytes，就是虚表的指针，前面我们说了虚函数表本质是一个
//	// 存虚函数指针的指针数组，这个数组最后面放了一个nullptr
//	// 1.先取b的地址，强转成一个int*的指针
//	// 2.再解引用取值，就取到了b对象头4bytes的值，这个值就是指向虚表的指针
//	// 3.再强转成VFPTR*，因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。
//	// 4.虚表指针传递给PrintVFT进行打印虚表
//	// 5.需要说明的是这个打印虚表的代码经常会崩溃，因为编译器有时对虚表的处理不干净，虚表最后面没有放nullptr，
//	//导致越界，这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案，再编译就好了。
//	VFPTR* ab = (VFPTR*)(*(int*)&b);
//	PrintVFT(ab);
//	VFPTR* ad = (VFPTR*)(*(int*)&d);
//	PrintVFT(ad);
//	VFPTR* ax = (VFPTR*)(*(int*)&x);
//	PrintVFT(ax);
//	return 0;
//}
//根据上述验证，所有虚函数都会放进虚函数表



//5.2 多继承中的虚函数表
class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};

class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive : public Base1, public Base2 {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};

typedef void(*VFPTR) ();
void PrintVFT(VFPTR a[]) {
	cout << "虚表地址>" << a << endl;
	for (int i = 0; a[i] != nullptr; ++i) {
		printf("第%d个虚函数地址：0x%p -> ", i + 1, a[i]);
		VFPTR f = a[i];
		f();
	}
	cout << endl;
}

int main() {
	Derive d;
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
	PrintVFT(vTableb1);

	//VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
	// //转换为char*是为了按字节计算偏移量
	// //+sizeof(Base1)指向Derive对象中Base2子对象的起始位置。
	//PrintVFT(vTableb2);
	Base2* ptr = &d;//直接将子类赋值给父类指针，发生切片。
	//ptr指向d对象中Base2的部分
	PrintVFT((VFPTR*)(*(int*)ptr));

	//多继承子类有两个虚表，且子类未重写的虚函数放在第一个继承父类部分的虚函数表中
	//为什么会有两个虚表？为了多态调用
	//如果只有一个虚表，无法切片
	Base1* p1 = &d;
	p1->func1();
	Base2* p2 = &d;
	p2->func1();
	//虽然p1和p2调用的都是d对象的func1，但是两个func1的地址不同
	//虽然Derive重写了两个func1。但p1和p2调用的又是同一个函数。
	
	//Base1和Base2各自有自己的虚表指针（vptr），分别指向不同的虚表。
	//Derive重写了func1()，这两个虚表中的func1条目都会指向Derive::func1()，但调用方式不同（需要调整this指针）。
	//p1指向Base1部分：直接调用，this指针就是Derive对象的Base1部分地址（不需要调整）。
	//p2指向Base2部分：p2当前指向的是Base2部分，但Derive::func1()需要this指针指向整个Derive对象的起始地址，
	//所以编译器会插入一段thunk代码来调整this指针，再跳转到真正的Derive::func1()。
	//结论：p1和p2调用的是同一个函数Derive::func1()，但它们的调用路径不同：
	//p1->func1() 直接调用。
	//p2->func1() 经过 thunk 调整 this 指针后再调用。
	//因此，它们的函数地址不同，但最终执行的是相同的代码。

	//为什么这么设计？
	//C++ 的对象内存布局是连续的，成员变量的偏移量是相对于对象起始地址计算的。
	//p2必须回到Derive的起始位置，才能正确访问所有成员
	//p2指向的是Base2子对象，而不是整个Derive对象的起始位置
	return 0;
}



//4.inline函数可以是虚函数吗？
//答：可以。多态调用时无内联属性，普通调用有内联属性。

//5. 静态成员可以是虚函数吗？
//答：不能，因为静态成员函数没有this指针，使用类型::成员函数的调用方式无法访问虚函数表，
//所以静态成员函数无法放进虚函数表。

//6. 构造函数可以是虚函数吗？
//答：不能，因为对象中的虚函数表指针是在构造函数初始化列表阶段初始化的。
//如果构造函数是虚函数，调用它需要查虚表，但此时虚表还未构造，导致矛盾

//7. 析构函数可以是虚函数吗？什么场景下析构函数是虚函数？
//答：可以，并且最好把基类的析构函数定义成虚函数。new对象时。

//8. 对象访问普通函数快还是虚函数更快？
//答：首先如果是普通调用，是一样快的。如果是多态调用，则调用的普通函数快，
//因为构成多态，运行时调用虚函数需要到虚函数表中去查找。

//9. 虚函数表是在什么阶段生成的，存在哪的？
//答：虚函数表是在编译阶段就生成的，一般情况下存在代码段(常量区)的。
